﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Threading;
using Pfz.DataTypes;
using Pfz.Extensions;
using Pfz.Factoring;

namespace Pfz.WpfControls
{
	/// <summary>
	/// A control for editing values using the right editor for the data-type.
	/// </summary>
	[ContentProperty("Value")]
	public class ValueControl:
		ContentCreatorControl,
		IValueControl,
		IHasDisplayName
	{
		/// <summary>
		/// RoutedEvent invoked when the value of this control changes.
		/// </summary>
		public static readonly RoutedEvent ValueChangedEvent =
			EventManager.RegisterRoutedEvent
			(
				"ValueChanged",
				RoutingStrategy.Direct,
				typeof(EventHandler<ValueChangedEventArgs>),
				typeof(ValueControl)
			);
			
		/// <summary>
		/// Event invoked when the change into the value of this control causes
		/// an error.
		/// </summary>
		public static readonly RoutedEvent ValueChangeThrownExceptionEvent = 
			EventManager.RegisterRoutedEvent
			(
				"ValueChangeThrownException",
				RoutingStrategy.Direct,
				typeof(EventHandler<ValueExceptionEventArgs>),
				typeof(ValueControl)
			);
	
		private IValueControl _innerValueControl;
		
		private Type _dataType;
		/// <summary>
		/// Gets or sets the DataType used by this ValueControl.
		/// </summary>
		public Type DataType
		{
			get
			{
				return _dataType;
			}
			set
			{
				_dataType = value;
				InvalidateContent();
			}
		}
		
		private string _displayName;
		/// <summary>
		/// Gets or sets the displayname of the control.
		/// </summary>
		public string DisplayName
		{
			get
			{
				return _displayName;
			}
			set
			{
				_displayName = value;
				InvalidateContent();
			}
		}
		
		/// <summary>
		/// Creates its content.
		/// </summary>
		protected override void  OnCreateContent()
		{
 			 base.OnCreateContent();

			var dataType = _dataType;
			if (dataType == null)
			{
				Content = null;
				return;
			}
			
			var content = ControlFactory.TryCreateFor(dataType, _displayName);
			if (content != null)
			{
				_innerValueControl = content.FirstOrDefault<IValueControl>();
				
				if (_isReadOnly)
					_innerValueControl.IsReadOnly = true;

				IHasValueChanged hasValueChanged = _innerValueControl as IHasValueChanged;
				if (hasValueChanged != null)
					hasValueChanged.ValueChanged += _ValueChanged;
			}
			else
			{
				content = new Label { Content = "There is not a registered editor for " + dataType.FullName + "." };
				_innerValueControl = null;
			}
				
			Content = content;
		}

		private object _oldValue;
		private void _ValueChanged(object sender, RoutedEventArgs args)
		{
			_CheckValueChanged();
		}
		private void _CheckValueChanged()
		{
			object newValue;
			try
			{
				newValue = Value;
			}
			catch(Exception exception)
			{
				ValueExceptionEventArgs args = new ValueExceptionEventArgs();
				args.Exception = exception;
				
				OnValueChangeThrownException(args);
				if (args.Handled)
					return;
				
				if (args.Exception == null)
					return;
				
				this.FocusFirstFocusable();

				if (args.Exception == exception)
					throw;
				
				throw args.Exception;
			}
			
			if (object.Equals(_oldValue, newValue))
				return;
			
			_oldValue = newValue;
			var args2 = new ValueChangedEventArgs();
			args2.WasChangedByUserAction = true;
			OnValueChanged(args2);
		}

		/// <summary>
		/// Method invoked when the processing of ValueChanged throws an exception.
		/// </summary>
		protected virtual void OnValueChangeThrownException(ValueExceptionEventArgs args)
		{
			args.RoutedEvent = ValueChangeThrownExceptionEvent;
			RaiseEvent(args);
		}

		/// <summary>
		/// Validates this controls.
		/// </summary>
		protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
		{
			base.OnLostKeyboardFocus(e);

			if (!IsVisible)
				return;

			bool mustCall = e.OldFocus == null || e.NewFocus == null;

			if (!mustCall)
			{
				var oldFocus = e.OldFocus as FrameworkElement;
				var newFocus = e.NewFocus as FrameworkElement;

				mustCall = oldFocus == null || newFocus == null;

				if (!mustCall)
					mustCall = oldFocus.GetParentOfType<ValueControl>() != this || newFocus.GetParentOfType<ValueControl>() != this;
			}
			
			if (mustCall)
			{
				Dispatcher.BeginInvokeOnce
				(
					_CheckValueChanged,
					DispatcherPriority.Send
				);
			}
		}
		
		/// <summary>
		/// Invoked when the value changes, be it by user action or code.
		/// </summary>
		protected virtual void OnValueChanged(ValueChangedEventArgs args)
		{
			args.RoutedEvent = ValueChangedEvent;
			RaiseEvent(args);
		}
		
		/// <summary>
		/// Event invoked when the value is changed, be it by user action
		/// or code.
		/// </summary>
		public event EventHandler<ValueChangedEventArgs> ValueChanged
		{
			add
			{
				AddHandler(ValueChangedEvent, value);
			}
			remove
			{
				RemoveHandler(ValueChangedEvent, value);
			}
		}
		
		/// <summary>
		/// Event invoked when the ValueChange process throws an exception.
		/// </summary>
		public event EventHandler<ValueExceptionEventArgs> ValueChangeThrownException
		{
			add
			{
				AddHandler(ValueChangeThrownExceptionEvent, value);
			}
			remove
			{
				RemoveHandler(ValueChangeThrownExceptionEvent, value);
			}
		}

		#region IValueContainer Members
			/// <summary>
			/// Gets or sets the value of the control.
			/// </summary>
			public object Value
			{
				get
				{
					return _innerValueControl.Value;
				}
				set
				{
					if (_dataType == null)
						if (value != null)
							_dataType = value.GetType();

					CreateContentNowIfNeeded();

					_SetValue(value);
				}
			}
			private void _SetValue(object value)
			{
				var innerControl = _innerValueControl;
				if (innerControl == null)
					return;
					
				innerControl.Value = value;
				
				// property is re-read, as between set and new get some difference
				// may exist.
				_oldValue = Value;
				
				var args = new ValueChangedEventArgs();
				OnValueChanged(args);
			}
		#endregion
		#region IValueControl Members
			/// <summary>
			/// Clears the value of this control.
			/// </summary>
			public void Clear()
			{
				if (_innerValueControl != null)
					_innerValueControl.Clear();
			}
			
			private bool _isReadOnly;
			/// <summary>
			/// Gets or sets a value indicating if this control is/should be read-only.
			/// </summary>
			public bool IsReadOnly
			{
				get
				{
					return _isReadOnly;
				}
				set
				{
					_isReadOnly = value;
					
					if (_innerValueControl != null)
						_innerValueControl.IsReadOnly = value;
				}
			}
		#endregion
	}

	/// <summary>
	/// Typed version of value control.
	/// </summary>
	public sealed class ValueControl<T>:
		ValueControl,
		IValueContainer<T>
	{
		/// <summary>
		/// Creates a new typed value control.
		/// </summary>
		public ValueControl()
		{
			DataType = typeof(T);
		}

		/// <summary>
		/// Gets or sets the value using the right type.
		/// </summary>
		public new T Value
		{
			get
			{
				return (T)base.Value;
			}
			set
			{
				base.Value = value;
			}
		}
	}
}
